{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "[this doc on github](https://github.com/dotnet/interactive/tree/main/samples/notebooks/fsharp/Samples)\n",
    "\n",
    "# dotnet/fsharp Github Dashboard <img src =\"https://upload.wikimedia.org/wikipedia/commons/thumb/3/38/Jupyter_logo.svg/207px-Jupyter_logo.svg.png\" width=\"60px\" alt=\"dotnet bot in space\" align =\"right\">\n",
    "\n",
    "### Add NuGet package references"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    }
   },
   "outputs": [],
   "source": [
    "#i \"nuget:https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json\" \r\n",
    "#i \"nuget:https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json\" \r\n",
    "\r\n",
    "#r \"nuget:Octokit, 0.32.0\"\r\n",
    "#r \"nuget:NodaTime, 2.4.6\"\r\n",
    "#r \"nuget: XPlot.Plotly.Interactive, 4.0.6\"\r\n",
    "\r\n",
    "open Octokit\r\n",
    "open NodaTime\r\n",
    "open NodaTime.Extensions\r\n",
    "open XPlot.Plotly"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Setup\n",
    "\n",
    "Create a GitHub public API client"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    }
   },
   "outputs": [],
   "source": [
    "let organization = \"dotnet\"\n",
    "let repositoryName = \"fsharp\"\n",
    "let options = ApiOptions()\n",
    "let gitHubClient = GitHubClient(ProductHeaderValue(\"notebook\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "[Generate a user token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) to get rid of public [api](https://github.com/octokit/octokit.net/blob/master/docs/getting-started.md) throttling policies for anonymous users "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    }
   },
   "outputs": [],
   "source": [
    "// let tokenAuth = Credentials(\"YOUR-TOKEN-HERE\")\n",
    "// gitHubClient.Credentials <- tokenAuth"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    }
   },
   "outputs": [],
   "source": [
    "let today = SystemClock.Instance.InUtc().GetCurrentDate()\n",
    "\n",
    "let startOfTheMonth = today.With(DateAdjusters.Month(1))\n",
    "\n",
    "let startOfTheYear = LocalDate(today.Year, 1, 1).AtMidnight()\n",
    "\n",
    "let since t = Nullable(DateTimeOffset(t))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Query GitHub for : \n",
    "- Issues created this month\n",
    "- Issues closed this month\n",
    "- Every issue this year"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    }
   },
   "outputs": [],
   "source": [
    "let createdIssuesRequest =\n",
    "    RepositoryIssueRequest(\n",
    "        Since = since (startOfTheMonth.ToDateTimeUnspecified()),\n",
    "        Filter = IssueFilter.Created)\n",
    "\n",
    "let closedIssuesRequest =\n",
    "    RepositoryIssueRequest(\n",
    "                Since = since (startOfTheMonth.ToDateTimeUnspecified()),\n",
    "                State = ItemStateFilter.Closed)\n",
    "\n",
    "let thisYearIssuesRequest =\n",
    "    RepositoryIssueRequest(\n",
    "        Since = since (startOfTheYear.ToDateTimeUnspecified()),\n",
    "        State = ItemStateFilter.All)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Start pulling data via the GitHub API"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    }
   },
   "outputs": [],
   "source": [
    "let createdThisMonthTask() =\n",
    "    gitHubClient.Issue.GetAllForRepository(organization, repositoryName, createdIssuesRequest)\n",
    "    |> Async.AwaitTask\n",
    "\n",
    "let closedThisMonthTask() =\n",
    "    gitHubClient.Issue.GetAllForRepository(organization, repositoryName, closedIssuesRequest)\n",
    "    |> Async.AwaitTask\n",
    "\n",
    "let thisYearIssuesTask() =\n",
    "    gitHubClient.Issue.GetAllForRepository(organization, repositoryName, thisYearIssuesRequest)\n",
    "    |> Async.AwaitTask\n",
    "\n",
    "let results =\n",
    "    [| createdThisMonthTask(); closedThisMonthTask(); thisYearIssuesTask() |]\n",
    "    |> Async.Parallel\n",
    "    |> Async.RunSynchronously\n",
    "\n",
    "let createdThisMonth = results.[0]\n",
    "let closedThisMonth = results.[1]\n",
    "let thisYearIssues = results.[2]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Group open and closed issues by month"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    }
   },
   "outputs": [],
   "source": [
    "let openSoFar =\n",
    "    createdThisMonth\n",
    "    |> Seq.sortBy (fun i -> i.CreatedAt)\n",
    "    |> Seq.filter (fun i -> i.State.StringValue = \"open\")\n",
    "\n",
    "let openByMonthOfCreation =\n",
    "    openSoFar\n",
    "    |> Seq.groupBy (fun i -> {| Year = i.CreatedAt.Year; Month = i.CreatedAt.Month |})\n",
    "    |> Seq.map (fun (key, issues) -> {| Date = key; Count = Seq.count issues |})\n",
    "    \n",
    "let closedSoFar =\n",
    "    thisYearIssues\n",
    "    |> Seq.sortBy (fun i -> i.CreatedAt)\n",
    "    |> Seq.filter (fun i -> i.State.StringValue = \"closed\")\n",
    "\n",
    "let closedByMonthOfClosure =\n",
    "    closedSoFar\n",
    "    |> Seq.groupBy (fun i -> {| Year = i.ClosedAt.Value.Year; Month = i.ClosedAt.Value.Month |})\n",
    "    |> Seq.map (fun (key, issues) ->  {| Date = key; Count = Seq.count issues |})\n",
    "\n",
    "let openCountByMonth =\n",
    "    let mutable runningTotal = thisYearIssues.Count\n",
    "    \n",
    "    closedSoFar\n",
    "    |> List.ofSeq\n",
    "    |> List.groupBy (fun i -> {| Year = i.CreatedAt.Year; Month = i.CreatedAt.Month |})\n",
    "    |> List.map (fun (key, issues) ->\n",
    "                   let dataPoint = {| Date = key; Count = Seq.count issues |}\n",
    "                   dataPoint)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Show issues opened this month grouped by day "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    }
   },
   "outputs": [],
   "source": [
    "let createdThisMonthByDay =\n",
    "    createdThisMonth\n",
    "    |> Seq.groupBy (fun i -> DateTime(i.CreatedAt.Year,i.CreatedAt.Month, i.CreatedAt.Day))\n",
    "    |> Seq.map (fun (date, issues) -> (date, issues.Count()))\n",
    "\n",
    "createdThisMonthByDay\n",
    "|> Chart.Line\n",
    "|> Chart.WithTitle(\"Daily created issues over the past year\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Show open issues, in descending order. Limit to 10 to save screen space."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    }
   },
   "outputs": [],
   "source": [
    "openSoFar\n",
    "|> Seq.map (fun i -> {| CreatedAt = i.CreatedAt; Title = i.Title; State = i.State.StringValue; Number = i.Number |})\n",
    "|> Seq.sortByDescending (fun d -> d.CreatedAt)\n",
    "|> Seq.take 10 // Limiting the output to 10 here!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's see what issues still opened, grouped by month, looks like"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    }
   },
   "outputs": [],
   "source": [
    "openByMonthOfCreation\n",
    "|> Seq.map (fun g -> (DateTime(g.Date.Year, g.Date.Month, 1), g.Count))\n",
    "|> Chart.Line\n",
    "|> Chart.WithTitle(\"Issues still opened, grouped by month\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's look at idle vs active issues."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    }
   },
   "outputs": [],
   "source": [
    "let idleByMonth =\n",
    "    openSoFar\n",
    "    |> Seq.filter (fun i -> i.Comments = 0)\n",
    "    |> Seq.groupBy (fun i -> DateTime(i.CreatedAt.Year, i.CreatedAt.Month, 1))\n",
    "    |> Seq.map(fun (key, issues) -> {| Date = key; Count = Seq.count issues |})\n",
    "\n",
    "let activeByMonth =\n",
    "    openSoFar\n",
    "    |> Seq.filter (fun i -> i.Comments > 0)\n",
    "    |> Seq.groupBy (fun i -> DateTime(i.CreatedAt.Year, i.CreatedAt.Month, 1))\n",
    "    |> Seq.map (fun (key, issues) -> {| Date = key; Count = Seq.count issues |})\n",
    "\n",
    "let idleSeries =\n",
    "    Graph.Scattergl(\n",
    "        name = \"Idle\",\n",
    "        y = (idleByMonth |> Seq.map (fun g -> g.Count)),\n",
    "        x = (idleByMonth |> Seq.map (fun g -> g.Date)))\n",
    "\n",
    "let activeSeries =\n",
    "    Graph.Scattergl(\n",
    "        name = \"Active\",\n",
    "        y = (activeByMonth |> Seq.map (fun g -> g.Count)),\n",
    "        x = (activeByMonth |> Seq.map (fun g -> g.Date)))\n",
    "\n",
    "[idleSeries; activeSeries]\n",
    "|> Chart.Plot\n",
    "|> Chart.WithTitle(\"Idle and active open issue report\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's generate a report for the whole year."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    }
   },
   "outputs": [],
   "source": [
    "let openDataPoints =\n",
    "    openByMonthOfCreation\n",
    "    |> Seq.map (fun g -> {| Date = DateTime(g.Date.Year, g.Date.Month, 1); Count = g.Count |})\n",
    "    |> Seq.sortBy (fun d -> d.Date)\n",
    "\n",
    "let closedDataPoints =\n",
    "    closedByMonthOfClosure\n",
    "    |> Seq.map (fun g -> {| Date = DateTime(g.Date.Year, g.Date.Month, 1); Count = g.Count |})\n",
    "    |> Seq.sortBy (fun d -> d.Date)\n",
    "\n",
    "let openCountByMonthDataPoints =\n",
    "    openCountByMonth\n",
    "    |> Seq.map (fun g -> {| Date = DateTime(g.Date.Year, g.Date.Month, 1); Count = g.Count |})\n",
    "    |> Seq.sortBy (fun d -> d.Date)\n",
    "\n",
    "let openSeries =\n",
    "    Graph.Scattergl(\n",
    "        name = \"Created\",\n",
    "        x = (openDataPoints |> Seq.map (fun g -> g.Date)),\n",
    "        y = (openDataPoints |> Seq.map (fun g -> g.Count)))\n",
    "\n",
    "let closeSeries =\n",
    "    Graph.Scattergl(\n",
    "        name = \"Closed\",\n",
    "        x = (closedDataPoints |> Seq.map (fun g -> g.Date)),\n",
    "        y = (closedDataPoints |> Seq.map (fun g -> g.Count)))\n",
    "\n",
    "let stillOpenSeries =\n",
    "    Graph.Scattergl(\n",
    "        name = \"Open\",\n",
    "        x = (openCountByMonthDataPoints |> Seq.map (fun g -> g.Date)),\n",
    "        y = (openCountByMonthDataPoints |> Seq.map (fun g -> g.Count)))\n",
    "\n",
    "[openSeries; closeSeries; stillOpenSeries]\n",
    "|> Chart.Plot\n",
    "|> Chart.WithTitle(\"Issue report for the current year\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### How many times has dotnet/fsharp been forked?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    }
   },
   "outputs": [],
   "source": [
    "let forks =\n",
    "    async {\n",
    "        return!\n",
    "            gitHubClient.Repository.Forks.GetAll(organization, repositoryName)\n",
    "            |> Async.AwaitTask\n",
    "    } |> Async.RunSynchronously"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "fsharp"
    }
   },
   "outputs": [],
   "source": [
    "let forksCreatedByMonth =\n",
    "    forks\n",
    "    |> Seq.groupBy (fun f -> DateTime(f.CreatedAt.Year, f.CreatedAt.Month,  f.CreatedAt.Day))\n",
    "    |> Seq.map (fun (key, issues) -> {| Date = key; Count = Seq.count issues |})\n",
    "    |> Seq.sortBy (fun g -> g.Date)\n",
    "\n",
    "let forksLastUpdateByMonth =\n",
    "    forks\n",
    "    |> Seq.groupBy (fun f -> DateTime(f.UpdatedAt.Year, f.UpdatedAt.Month,  f.UpdatedAt.Day))\n",
    "    |> Seq.map (fun (key, issues) -> {| Date = key; Count = Seq.count issues |})\n",
    "    |> Seq.sortBy (fun g -> g.Date)\n",
    "\n",
    "let forkCountByMonth =\n",
    "    forksCreatedByMonth\n",
    "    |> Seq.sortBy (fun g -> g.Date)\n",
    "    |> Seq.map (fun g -> {| Date = g.Date; Count = g.Count |})\n",
    "\n",
    "let forkCreationSeries =\n",
    "    Graph.Scattergl(\n",
    "        name = \"created by month\",\n",
    "        x = (forksCreatedByMonth |> Seq.map (fun g -> g.Date)),\n",
    "        y = (forksCreatedByMonth |> Seq.map (fun g -> g.Count)))\n",
    "\n",
    "let forkTotalSeries =\n",
    "    Graph.Scattergl(\n",
    "        name = \"running total\",\n",
    "        x = (forkCountByMonth |> Seq.map (fun g -> g.Date)),\n",
    "        y = (forkCountByMonth |> Seq.map (fun g -> g.Count)))\n",
    "\n",
    "let forkUpdateSeries =\n",
    "    Graph.Scattergl(\n",
    "        name = \"last updated by month\",\n",
    "        x = (forksLastUpdateByMonth |> Seq.map (fun g -> g.Date)),\n",
    "        y = (forksLastUpdateByMonth |> Seq.map (fun g -> g.Count)))\n",
    "\n",
    "[forkCreationSeries; forkTotalSeries; forkUpdateSeries]\n",
    "|> Chart.Plot\n",
    "|> Chart.WithTitle(\"Fork activity\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".NET (F#)",
   "language": "F#",
   "name": ".net-fsharp"
  },
  "language_info": {
   "file_extension": ".fs",
   "mimetype": "text/x-fsharp",
   "name": "C#",
   "pygments_lexer": "fsharp",
   "version": "4.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}